"""Tornado handlers for the contents web service.

Preliminary documentation at https://github.com/ipython/ipython/wiki/IPEP-27%3A-Contents-Service
"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

import json
import os
from tornado import gen, web
from .MyCode import get_config_dirs
from notebook.utils import maybe_future, url_path_join, url_escape
from jupyter_client.jsonutil import date_default
from ipython_genutils import py3compat

from notebook.base.handlers import (
    IPythonHandler, APIHandler, path_regex,
)


def validate_model(model, expect_content):
    """
    Validate a model returned by a ContentsManager method.

    If expect_content is True, then we expect non-null entries for 'content'
    and 'format'.
    """
    required_keys = {
        "name",
        "path",
        "type",
        "writable",
        "created",
        "last_modified",
        "mimetype",
        "content",
        "format",
    }
    missing = required_keys - set(model.keys())
    if missing:
        raise web.HTTPError(
            500,
            u"Missing Model Keys: {missing}".format(missing=missing),
        )

    maybe_none_keys = ['content', 'format']
    if expect_content:
        errors = [key for key in maybe_none_keys if model[key] is None]
        if errors:
            raise web.HTTPError(
                500,
                u"Keys unexpectedly None: {keys}".format(keys=errors),
            )
    else:
        errors = {
            key: model[key]
            for key in maybe_none_keys
            if model[key] is not None
        }
        if errors:
            raise web.HTTPError(
                500,
                u"Keys unexpectedly not None: {keys}".format(keys=errors),
            )


class ContentsHandler(APIHandler):

    def location_url(self, path):
        """Return the full URL location of a file.

        Parameters
        ----------
        path : unicode
            The API path of the file, such as "foo/bar.txt".
        """
        return url_path_join(
            self.base_url, 'api', 'contents', url_escape(path)
        )

    def _finish_model(self, model, location=True):
        """Finish a JSON request with a model, setting relevant headers, etc."""
        if location:
            location = self.location_url(model['path'])
            self.set_header('Location', location)
        self.set_header('Last-Modified', model['last_modified'])
        self.set_header('Content-Type', 'application/json')
        self.finish(json.dumps(model, default=date_default))

    @web.authenticated
    @gen.coroutine
    def get(self, path=''):
        """
        根据所传路径,返回两极文件列表
        :param path:
        :return:
        """
        """Return a model for a file or directory.

        A directory model contains a list of models (without content)
        of the files and directories it contains.
        """
        # 将username赋值到环境变量，每次触发查询后更新（伪实时）
        # with open("E:\\PycharmProjects\\notebookForUsers\\Username", "r", encoding="utf-8") as f:
        #     username = f.read()
        # os.environ["USERNAME"] = username
        userId = self.request.headers.get('Referer', 'None').split("/")[-1].split("=")[-1]
        path = path or ""
        if path:
            # self.log.debug(f"pppppp{path}")
            type = self.get_query_argument('type', default=None)
            if type not in {None, 'directory', 'file', 'notebook'}:
                raise web.HTTPError(400, u'Type %r is invalid' % type)

            format = self.get_query_argument('format', default=None)
            if format not in {None, 'text', 'base64'}:
                raise web.HTTPError(400, u'Format %r is invalid' % format)
            content = self.get_query_argument('content', default='1')
            if content not in {'0', '1'}:
                raise web.HTTPError(400, u'Content %r is invalid' % content)
            content = int(content)

            model = yield maybe_future(self.contents_manager.get(
                path=path, type=type, format=format, content=content,
            ))

            # 重组Name显示输出
            try:
                if model:
                    model["name"] = model.get("name").replace(f"@{userId}", "")
                    if model.get("content"):
                        for i in model.get("content"):
                            i["name"] = i.get("name").replace(f"@{userId}", "")
            except Exception as e:
                self.log.debug(f"Debug!!!!!!!!{e}")
            validate_model(model, expect_content=content)
            self.set_header('Last-Modified', model['last_modified'])
            self.set_header('Content-Type', 'application/json')
            self.finish(json.dumps(model, default=date_default))
        else:
            username_path = os.path.join(py3compat.getcwd(), 'WorkFiles')
            if not os.path.exists(username_path):
                os.mkdir(username_path)
            model = get_config_dirs(username_path, userId)
            self.log.debug(model)
            self.set_header('Last-Modified', model['last_modified'])
            self.set_header('Content-Type', 'application/json')
            self.finish(json.dumps(model, default=date_default))

    @web.authenticated
    @gen.coroutine
    def patch(self, path=''):
        """PATCH renames a file or directory without re-uploading content."""
        userId = self.request.headers.get('Referer', 'None').split("/")[-1].split("=")[-1]
        cm = self.contents_manager
        model = self.get_json_body()
        if model is None:
            raise web.HTTPError(400, u'JSON body missing')
        model_path_name = model.get("path")
        model_path_name = model_path_name.split(".")
        model_path_fix = ""
        # 若特殊后缀名，则认为是文件，进行特殊处理
        if os.path.isfile(os.path.join(py3compat.getcwd(), 'WorkFiles' + path)) and "." in model_path_name:
            model_path_fix = "." + model_path_name.pop(-1)
        model_path_name = ".".join(filter(None, model_path_name))
        # 修改后续名称
        model_path_name = model_path_name + "@" + userId + model_path_fix
        model["path"] = model_path_name
        # 修改路径名
        model = yield maybe_future(cm.update(model, path))
        validate_model(model, expect_content=False)
        self._finish_model(model)

    @gen.coroutine
    def _copy(self, copy_from, copy_to=None):
        """Copy a file, optionally specifying a target directory."""
        self.log.info(u"Copying {copy_from} to {copy_to}".format(
            copy_from=copy_from,
            copy_to=copy_to or '',
        ))
        model = yield maybe_future(self.contents_manager.copy(copy_from, copy_to))
        self.set_status(201)
        validate_model(model, expect_content=False)
        self._finish_model(model)

    @gen.coroutine
    def _upload(self, model, path):
        """Handle upload of a new file to path"""
        self.log.info(u"Uploading file to %s", path)

        model = yield maybe_future(self.contents_manager.new(model, path))
        self.set_status(201)
        validate_model(model, expect_content=False)
        self._finish_model(model)

    @gen.coroutine
    def _new_untitled(self, path, type='', ext=''):
        """Create a new, empty untitled entity"""
        self.log.info(u"Creating new %s in %s", type or 'file', path)
        model = yield maybe_future(self.contents_manager.new_untitled(path=path, type=type, ext=ext))
        self.set_status(201)
        validate_model(model, expect_content=False)
        self._finish_model(model)

    @gen.coroutine
    def _save(self, model, path):
        """Save an existing file."""
        chunk = model.get("chunk", None)
        if not chunk or chunk == -1:  # Avoid tedious log information
            self.log.info(u"Saving file at %s", path)
        model = yield maybe_future(self.contents_manager.save(model, path))
        validate_model(model, expect_content=False)
        self._finish_model(model)

    @web.authenticated
    @gen.coroutine
    def post(self, path=''):
        """Create a new file in the specified path.

        POST creates new files. The server always decides on the name.

        POST /api/contents/path
          New untitled, empty file or directory.
        POST /api/contents/path
          with body {"copy_from" : "/path/to/OtherNotebook.ipynb"}
          New copy of OtherNotebook in path
        """
        # with open("E:\\PycharmProjects\\notebookForUsers\\Username", "r", encoding="utf-8") as f:
        #     username = f.read()
        # os.environ["USERNAME"] = username
        userId = self.request.headers.get('Referer', 'None').split("/")[-1].split("=")[-1]
        cm = self.contents_manager
        # 修改文件/文件夹路径
        cm.untitled_directory = "Untitled Folder" + "@" + userId
        cm.untitled_file = 'untitled' + "@" + userId
        cm.untitled_notebook = 'Untitled' + "@" + userId
        file_exists = yield maybe_future(cm.file_exists(path))
        if file_exists:
            raise web.HTTPError(400, "Cannot POST to files, use PUT instead.")

        dir_exists = yield maybe_future(cm.dir_exists(path))
        if not dir_exists:
            raise web.HTTPError(404, "No such directory: %s" % path)

        model = self.get_json_body()

        if model is not None:
            copy_from = model.get('copy_from')
            ext = model.get('ext', '')
            type = model.get('type', '')
            if copy_from:
                yield self._copy(copy_from, path)
            else:
                yield self._new_untitled(path, type=type, ext=ext)
        else:
            yield self._new_untitled(path)
        # 回退初始状态
        cm.untitled_directory = "Untitled Folder"
        cm.untitled_file = 'untitled'
        cm.untitled_notebook = 'Untitled'

    @web.authenticated
    @gen.coroutine
    def put(self, path=''):
        """Saves the file in the location specified by name and path.

        PUT is very similar to POST, but the requester specifies the name,
        whereas with POST, the server picks the name.

        PUT /api/contents/path/Name.ipynb
          Save notebook at ``path/Name.ipynb``. Notebook structure is specified
          in `content` key of JSON request body. If content is not specified,
          create a new empty notebook.
        """
        userId = self.request.headers.get('Referer', 'None').split("/")[-1].split("=")[-1]
        model = self.get_json_body()
        if model:
            if model.get('copy_from'):
                raise web.HTTPError(400, "Cannot copy with PUT, only POST")
            exists = yield maybe_future(self.contents_manager.file_exists(path))
            if exists:
                yield maybe_future(self._save(model, path))
            else:
                # 上传前更换存储路径
                path_list = list(os.path.split(path))
                file_name = path_list.pop(-1).split(".")
                file_fix = "." + file_name.pop(-1)
                file_name = ".".join(file_name) + "@" + userId + file_fix
                path = ""
                for p in path_list:
                    path = os.path.join(path, p)
                path = os.path.join(path, file_name)
                model["path"] = path
                yield maybe_future(self._upload(model, path))
        else:
            yield maybe_future(self._new_untitled(path))

    @web.authenticated
    @gen.coroutine
    def delete(self, path=''):
        """delete a file in the given path"""
        cm = self.contents_manager
        self.log.warning('delete %s', path)
        yield maybe_future(cm.delete(path))
        self.set_status(204)
        self.finish()


class CheckpointsHandler(APIHandler):

    @web.authenticated
    @gen.coroutine
    def get(self, path=''):
        """get lists checkpoints for a file"""
        cm = self.contents_manager
        checkpoints = yield maybe_future(cm.list_checkpoints(path))
        data = json.dumps(checkpoints, default=date_default)
        self.finish(data)

    @web.authenticated
    @gen.coroutine
    def post(self, path=''):
        """post creates a new checkpoint"""
        cm = self.contents_manager
        checkpoint = yield maybe_future(cm.create_checkpoint(path))
        data = json.dumps(checkpoint, default=date_default)
        location = url_path_join(self.base_url, 'api/contents',
                                 url_escape(path), 'checkpoints', url_escape(checkpoint['id']))
        self.set_header('Location', location)
        self.set_status(201)
        self.finish(data)


class ModifyCheckpointsHandler(APIHandler):

    @web.authenticated
    @gen.coroutine
    def post(self, path, checkpoint_id):
        """post restores a file from a checkpoint"""
        cm = self.contents_manager
        yield maybe_future(cm.restore_checkpoint(checkpoint_id, path))
        self.set_status(204)
        self.finish()

    @web.authenticated
    @gen.coroutine
    def delete(self, path, checkpoint_id):
        """delete clears a checkpoint for a given file"""
        cm = self.contents_manager
        yield maybe_future(cm.delete_checkpoint(checkpoint_id, path))
        self.set_status(204)
        self.finish()


class NotebooksRedirectHandler(IPythonHandler):
    """Redirect /api/notebooks to /api/contents"""
    SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH', 'POST', 'DELETE')

    def get(self, path):
        self.log.warning("/api/notebooks is deprecated, use /api/contents")
        self.redirect(url_path_join(
            self.base_url,
            'api/contents',
            path
        ))

    put = patch = post = delete = get


class TrustNotebooksHandler(IPythonHandler):
    """ Handles trust/signing of notebooks """

    @web.authenticated
    @gen.coroutine
    def post(self, path=''):
        cm = self.contents_manager
        yield maybe_future(cm.trust_notebook(path))
        self.set_status(201)
        self.finish()


# -----------------------------------------------------------------------------
# URL to handler mappings
# -----------------------------------------------------------------------------

_checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"

default_handlers = [
    (r"/api/contents%s/checkpoints" % path_regex, CheckpointsHandler),
    (r"/api/contents%s/checkpoints/%s" % (path_regex, _checkpoint_id_regex),
     ModifyCheckpointsHandler),
    (r"/api/contents%s/trust" % path_regex, TrustNotebooksHandler),
    (r"/api/contents%s" % path_regex, ContentsHandler),
    (r"/api/notebooks/?(.*)", NotebooksRedirectHandler),
]
